#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utypes.h>
#include "window.h"
#include "level.h"
#include "heap.h"
#include "alexkidd.h"
#include "enemies.h"
#include "boss.h"

/** Número de controlo da versão do nível */
static unsigned int level_control = 0;

/** Número de testes de colisão que fazemos a cada iteração
 */
#define NUMBER_COLLISION_TESTS 8

/** Sprites no nível */
struct LevelSprite_ {
	Window win; /**< Janela do sprite */
	int radius; /**< Distância a partir do centro da janela onde podem haver colisões */
	int flags;  /**< Flags do sprite */
	char schar; /**< Caracter correspondente ao sprite */

	int cell_w; /**< Largura da célula no sistema de coordenadas do ecrã */
	int cell_h; /**< Altura da célula no sistema de coordenadas do ecrã */

	int x;	    /**< Coordenada X original do sprite no mapa */
	int y;	    /**< Coordenada Y original do sprite no mapa */

	int wx;	    /**< Coordenada X original do sprite no ecrã */
	int wy;	    /**< Coordenada Y original do sprite no ecrã */
	LevelSpriteCallBack killed; /**< Função chamada quando o sprite morre */
	LevelSpriteCallBack unload; /**< Função chamada quando o nível é destruído */
	void *data; /**< Dados associados ao sprite */
	struct LevelSprite_ *next; /**< Próximo sprite */
};

/** Largura do nível */
static int level_w;

/** Altura do nível */
static int level_h;

/** Células do nível */
static LevelCell **cells;

/** Há um nível carregado? */
static Bool loaded = false;

/** Heap usado para testar colisões */
static Heap collider_heap;

/** Sprite do Alex Kidd */
static LevelSprite alex_sprite;

/** Função que obtém a distância entre dois sprites
 *
 * \param spr1 Primeiro sprite
 * \param spr2 Segundo sprite
 * \return A distância entre os dois sprites
 */
static int sprite_dist (LevelSprite spr1, LevelSprite spr2)
{
	int x1, y1, w1, h1, x2, y2, w2, h2;

	// Obtém o retângulo das janelas
	window_get_rect (spr1->win, &x1, &y1, &w1, &h1);
	window_get_rect (spr2->win, &x2, &y2, &w2, &h2);
	// Obtém os centros das janelas
	x1 += w1/2;
	y1 += h1/2;
	x2 += w2/2;
	y2 += h2/2;
	// Obtém a distância Manhattan das janelas
	return abs(x1 - x2) + abs (y1 - y2);
}

/** Função de comparação de elementos do heap */
static int collider_func (void *o1, void *o2) 
{
	LevelSprite ls1 = o1;
	LevelSprite ls2 = o2;
	// Quanto mais próximo do Alex, maior a prioridade
	return  sprite_dist(alex_sprite, ls1) - sprite_dist(alex_sprite, ls2);
}


/** Lê uma linha da entrada */
static char *get_line (FILE *fp)
{
	int asize = 128; // Tamanho alocado
	int i = 0; // Índice
	int c; // Caracter lido
	char *str = malloc (asize + 1); // String lida

	c = getc (fp);
	while (c > 0 && c != '\n') {
		if (i >= asize) {
			asize *= 2;
			str = realloc (str, asize + 1);
		}
		str[i] = c;
		i++;
		c = getc (fp);
	}
	str [i] = '\0';
	return str;
}

/** Carregador de níveis
 *
 * \param file Ficheiro com o nível
 * \todo Alterar quando tivermos coisas que usam os sprites de nível
 */
static void loader (FILE *fp)
{
	level_w = 0;
	level_h = 0;

	int *row_len; // Comprimento das linhas de cells
	int i;
	LevelSprite sprite = NULL;
	// Enquanto há dados a serem lidos
	while (! feof(fp)) {
		char *str = get_line (fp);
		int len = strlen (str);

		if (len == 0) {
			free (str);
			continue;
		}

		// Aumenta os arrays
		if (level_h > 0) {
			cells = realloc (cells, (level_h + 1) * sizeof ( LevelCell * ));
			row_len = realloc (row_len, (level_h + 1) * sizeof ( int ));
		} else {
			cells = malloc (sizeof ( LevelCell * ));
			row_len = malloc (sizeof ( int ));
		}

		
		// aumenta a largura do nível se necessário
		if (len > level_w) 
			level_w = len;

		// Aloca uma linha de células
		cells[level_h] = malloc (len * sizeof ( LevelCell ));
		row_len[level_h] = len;
		
		/* Copia o valor para a célula */
		for (i = 0; i < len; i++)
			cells[level_h][i] = str[i];
		free (str);
		level_h ++;
	}

	// Corrige o comprimento das linhas
	for (i = 0; i < level_h; i++) {
		if (row_len [i] != level_w) {
			cells[i] = realloc (cells[i], level_w * sizeof ( LevelCell ));
			int j;
			for (j = row_len [i]; j < level_w; j++)
				cells[i][j] = LEVEL_EMPTY;
		}
	}
	free (row_len);
	// Transforma o mapa lido no mapa usado no jogo
	int x, y;

/** Macro local que armazena o sprite. Após a chamada desta macro o sprite
 * pode ser encontrado na variável sprite
 *
 * \param FLAGS Flags do sprite
 */
#define STORE_SPRITE(FLAGS) {\
 	cells[y][x] = (FLAGS); \
	LevelSprite aux = malloc (sizeof (struct LevelSprite_)); \
	aux->next = sprite; \
	sprite = aux; \
	sprite->flags = (FLAGS); \
	sprite->schar = cells[y][x]; \
	sprite->x = x; \
	sprite->y = y; \
}

	for (y = 0; y < level_h; y++) {
		for (x = 0; x < level_w; x++) {
			switch (cells[y][x])  {
				case 'A': // Alex kidd
					STORE_SPRITE(LEVEL_ALEXK);
					alex_sprite = sprite;
					sprite = sprite->next;

					alex_sprite_init (x, y, alex_sprite);
					break;

				// Inimigos
				case 'V': // águia
					STORE_SPRITE(LEVEL_DEATH | LEVEL_ALIVE);
					eagle_sprite_init (x, y, sprite);
					break;


				case '#': // Parede
					cells[y][x] = LEVEL_WALL;
					break;

				// Sprites frágeis
				case 'C': // Bolo
					STORE_SPRITE(LEVEL_FRAGILE);
					cake_sprite_init (x, y, sprite);
					break;

				case 'B': // Boss
					STORE_SPRITE(LEVEL_FRAGILE);
					boss_sprite_init (x, y, sprite);
					break;
				case 'S': // Dinheiro pequeno
					STORE_SPRITE(LEVEL_FRAGILE);
					money_s_sprite_init (x, y, sprite);
					break;

				default: // Espaço vazio
					cells[y][x] = LEVEL_EMPTY;
			}
		}
	}
#undef STORE_SPRITE

	// Põe todos os sprites no heap
	while (sprite != NULL) {
		heap_push (collider_heap, sprite);
		sprite = sprite->next;
	}
	loaded = true;
}


void level_load (const char *file)
{
	level_control++;

	if (loaded)
		level_unload();

	collider_heap = heap_new (collider_func);
	alex_sprite = NULL;
	FILE *fp = fopen (file, "r");
	if (fp == NULL)
		return;
	loader (fp);
	fclose (fp);
}


void level_unload (void)
{
	level_control++;

	int i;
	LevelSprite ls;
	
	if (!loaded)
		return;

	// Destroi os sprites
	heap_push (collider_heap, alex_sprite);
	while ((ls = heap_pop (collider_heap)) != NULL) {
		ls->unload (ls->win, ls->data);
		free (ls);
	}
	heap_delete (collider_heap);

	// Destroi o mapa	
	for (i = 0; i < level_h; i++) {
		free (cells[i]);
	}
	free (cells);
	loaded = false;
}

/** Põe os sprites do heap na estrutura
 */
static void heap_place_sprites (void *spr_ptr, void *data_ptr)
{
	LevelSprite spr = spr_ptr;
	char *data = data_ptr;

	// Obtém os dados da janela
	int wx, wy, ww, wh;
	window_get_rect (spr->win, &wx, &wy, &ww, &wh);

	// Variação no sistema de coordenadas do ecrã
	int dx = wx - spr->wx;
	int dy = wy - spr->wy;

	// Converte para o sistema de coordenadas do mapa
	dx = dx / spr->cell_w;
	dy = dy / spr->cell_h;

	int x = spr->x + dx;
	int y = spr->y + dy;

	data [y * level_w + x] = spr->schar;
}

/* Grava o nível */
Bool level_save (const char *file)
{
	// Constrói a estrutura de dados adequada
	char data [level_h * level_w];
	int x, y;

	// Põe as paredes
	for (y = 0; y < level_h; y++) {
		for (x = 0; x < level_w; x++) {
			if ( cells[y][x] == LEVEL_WALL ) {
				data[y * level_w + x] = '#';
			} else {
				data[y * level_w + x] = ' ';
			}
		}	
	}
	// Põe os sprites no mapa
	heap_forall (collider_heap, heap_place_sprites, data);
	heap_place_sprites (alex_sprite, data);

	FILE *fp = fopen (file, "w");
	if (fp == NULL) {
		return false;
	}
	for (y = 0; y < level_h; y++) {
		for (x = 0; x < level_w; x++) {
			putc (data[y * level_w + x], fp);
		}	
		putc ('\n', fp);
	}
	fclose (fp);
	return true;
}

Bool level_get (LevelCell ***map_addr, int *width_addr, int *height_addr)
{
	if (map_addr != NULL)
		*map_addr = cells;
	if (width_addr != NULL)
		*width_addr = level_w;
	if (height_addr != NULL)
		*height_addr = level_h;
	return loaded;
}

void level_sprite_init (LevelSprite ls,
			Window win,
			LevelSpriteCallBack killed,
			LevelSpriteCallBack unload,
			int cell_w,
			int cell_h,
			void *data)
{
	ls->win = win;
	ls->killed = killed;
	ls->unload = unload;
	ls->cell_w = cell_w;
	ls->cell_h = cell_h;
	ls->data = data;

	int wx, wy, ww, wh;
	window_get_rect (win, &wx, &wy, &ww, &wh);
	ls->wx = wx;
	ls->wy = wy;
	ls->radius = (ww + wh)/2; // Distãncia do centro ao canto na norma Manhattan
}

void level_sprite_set_flag (LevelSprite ls, enum CellFlags flag)
{
	ls->flags = ls->flags | flag;
}

void level_sprite_unset_flag (LevelSprite ls, enum CellFlags flag)
{
	ls->flags = ls->flags & ~flag;
}


/* Testa se uma posição no sistema de coordenadas do ecrã bloqueia movimentos */
Bool level_block (int x, int y)
{
	// Bloqueia se está saindo do mapa
	if (x < 0 || y < 0 || x >= level_w || y >= level_h) {
		return true;
	}
	return cells[y][x] & LEVEL_BLOCK;
}

/** Testa se dois sprites colidem
 *
 * \param spr1 Primeiro sprite
 * \param spr2 Segundo sprite
 * \return Verdadeiro se os sprites colidem
 */
static Bool sprite_collide (LevelSprite spr1, LevelSprite spr2)
{
	int x1, y1, w1, h1, x2, y2, w2, h2;

	// Obtém o retângulo das janelas
	window_get_rect (spr1->win, &x1, &y1, &w1, &h1);
	window_get_rect (spr2->win, &x2, &y2, &w2, &h2);

	// Há intersecção em X?
	if ( x1 == x2 || (x1 > x2 && x1 < x2 + w2) || (x2 > x1 && x2 < x1 + w1)) {
		// Há intersecção em y?
		if ( y1 == y2 || (y1 > y2 && y1 < y2 + h2) || (y2 > y1 && y2 < y1 + h1)) {
			return true;
		}
	}
	return false;
}


/* Faz a colisão entre sprites */
void level_collision (void)
{
	// Termina se não há nível carregado
	if (!loaded) return;

	LevelSprite sprites [NUMBER_COLLISION_TESTS]; 
	int i = 0;

	int control = level_control;

	/* Enquanto devemos testar */
	while (i < NUMBER_COLLISION_TESTS // Enquanto está próximo
		&& heap_size (collider_heap) != 0 // E existem mais sprites a testar
		&& control == level_control) { // e o nível não foi mudado por algum sprite

		sprites[i] = heap_pop (collider_heap);
		// Se estão colidindo

		if (sprite_collide (alex_sprite, sprites[i])) {

			if ( sprites[i]->flags & LEVEL_FRAGILE ) {
				// Se o sprite é frágil e morre sempre que uma colisão ocorre
				sprites[i]->killed (sprites[i]->win, sprites[i]->data);
				free (sprites[i]);
				break;

			} else if ( (alex_sprite->flags & LEVEL_DEATH)
					&& (sprites[i]->flags & LEVEL_ALIVE)) {
				// Alex Kidd matou um inimigo
				sprites[i]->killed (sprites[i]->win, sprites[i]->data);
				free (sprites[i]);
				break;

			} else if (!(alex_sprite->flags & LEVEL_DEATH)
					&& (sprites[i]->flags & LEVEL_DEATH)) {
				// Alex Kidd morreu
				alex_sprite->killed (alex_sprite->win, alex_sprite->data);
				i++;
				break;
			}
		}
		i++;
	}

	int j;
	// Reinsere os sprites no heap se o nível não mudou
	if (control == level_control) {
		for (j = 0; j < i; j++) {
			heap_push (collider_heap, sprites[j]);
		}
	} else { // Senão os descarrega
		for (j = 0; j < i; j++) {
			sprites[j]->unload (sprites[j]->win, sprites[j]->data);
			free (sprites[j]);
		}
	}
}

void level_callback_delwin (Window win, void *data)
{
	window_delete (win);
}

